﻿namespace VividKit.PresentationModel.Navigation
{
	using System;
	using System.Linq;
	using System.Windows;
	using System.Windows.Controls;
	using System.Windows.Navigation;
	using Microsoft.Phone.Controls;
	using VividKit.Controls;

	/// <summary>
	/// Provides default implementation for the <see cref="INavigationService"/> interface.
	/// </summary>
	public class FrameNavigationService : INavigationService
	{
		private readonly Frame frame;

		/// <summary>
		/// Initializes a new instance of the <see cref="FrameNavigationService"/> class.
		/// </summary>
		/// <param name="navigator">The navigator view model.</param>
		/// <param name="frame">The navigation service.</param>
		public FrameNavigationService(Frame frame)
		{
			this.frame = frame;
		}

		/// <summary>
		/// Gets a value that indicates whether there is at least one entry in back navigation history.
		/// </summary>
		/// <value>
		/// 	<c>true</c> if there is at least one entry in back navigation history; otherwise, <c>false</c>.
		/// </value>
		public bool CanGoBack
		{
			get
			{
#if WINDOWS_PHONE
				return this.frame.CanGoBack && ((PhoneApplicationFrame)this.frame).BackStack.Any();
#else
				return this.frame.CanGoBack;
#endif
			}
		}

		/// <summary>
		/// Navigates to the most recent entry in back navigation history, if there is one.
		/// </summary>
		public void GoBack()
		{
			this.frame.GoBack();
		}

		/// <summary>
		/// Navigates to a view associated with the specified <see cref="T:NavigatorViewModel">context</see>.
		/// </summary>
		/// <param name="source">The source.</param>
		/// <param name="targetType">Type of the target.</param>
		/// <param name="target">The target view name.</param>
		/// <param name="parameter">The parameter.</param>
		public void Navigate(INavigable source, Type targetType, string target, object parameter)
		{
			NavigatedEventHandler navigatedHandler = null;
			NavigationFailedEventHandler navigationFailedHanler = null;
			NavigationStoppedEventHandler navigationStoppedHandler = null;

			navigatedHandler = (sender, e) =>
			{
				this.frame.Navigated -= navigatedHandler;
				this.frame.NavigationFailed -= navigationFailedHanler;
				this.frame.NavigationStopped -= navigationStoppedHandler;

				HandleServiceNavigatedToContent(source, e, parameter);
			};

			navigationFailedHanler = (sender, e) =>
			{
				this.frame.Navigated -= navigatedHandler;
				this.frame.NavigationFailed -= navigationFailedHanler;
				this.frame.NavigationStopped -= navigationStoppedHandler;
			};

			navigationStoppedHandler = (sender, e) =>
			{
				this.frame.Navigated -= navigatedHandler;
				this.frame.NavigationFailed -= navigationFailedHanler;
				this.frame.NavigationStopped -= navigationStoppedHandler;
			};

			this.frame.Navigated += navigatedHandler;
			this.frame.NavigationFailed += navigationFailedHanler;
			this.frame.NavigationStopped += navigationStoppedHandler;

			var contentUri = NavigableAttribute.GetContentUri(target, targetType, parameter);
			var navigationStarted = this.frame.Navigate(contentUri);

			if (navigationStarted == false)
			{
				this.frame.Navigated -= navigatedHandler;
				this.frame.NavigationFailed -= navigationFailedHanler;
				this.frame.NavigationStopped -= navigationStoppedHandler;
			}
		}

		/// <summary>
		/// Remove the most recent entry from the back stack.
		/// </summary>
		public void RemoveBackEntry()
		{
#if WINDOWS_PHONE
			((PhoneApplicationFrame)this.frame).RemoveBackEntry();
#endif
		}

		private void HandleServiceNavigatedToContent(INavigable source, NavigationEventArgs e, object extraData)
		{
#if WINDOWS_PHONE == false
			source.OnNavigatedFrom(e);
#endif

			var element = e.Content as FrameworkElement;
			if (element != null)
			{
				NavigatorService.SetExtraData(element, extraData);

#if WINDOWS_PHONE == false
				RoutedEventHandler elementLoadedHandler = null;
				elementLoadedHandler = delegate
				{
					element.Loaded -= elementLoadedHandler;

					var target = element.DataContext as INavigable;
					if (target != null)
					{
						target.OnNavigatedTo(e);
						target.OnContextEstablished(new NavigationContext(e, extraData));
					}
				};

				if (element.DataContext != null)
				{
					elementLoadedHandler(element, null);
				}
				else
				{
					element.Loaded += elementLoadedHandler;
				}
#endif
			}
		}
	}
}